{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50ac7271",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, TypedDict, List, Dict, Any, Optional, Literal\n",
    "from langchain_core.messages import AIMessage, HumanMessage, SystemMessage\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_community.agent_toolkits import PlayWrightBrowserToolkit\n",
    "from langchain_community.tools.playwright.utils import create_async_playwright_browser\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.prebuilt import ToolNode, tools_condition\n",
    "from langgraph.graph.message import add_messages\n",
    "from pydantic import BaseModel, Field\n",
    "from IPython.display import Image, display\n",
    "import nest_asyncio\n",
    "import gradio as gr\n",
    "import uuid\n",
    "from dotenv import load_dotenv"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d4359c17",
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a0458fed",
   "metadata": {},
   "outputs": [],
   "source": [
    "INITIAL = \"initial\"\n",
    "GENERATE_CLARIFICATION_QUERY = \"generate_clarification_query\"\n",
    "CONFIRM_COMPETITORS = \"confirm_competitors\"\n",
    "GET_COMPETITORS = \"get_competitors\"\n",
    "GET_COMPETITORS_PROMPT = \"get_competitors_prompt\"\n",
    "GET_REPORT = \"get_report\"\n",
    "FINAL = \"final\"\n",
    "THREAD_ID = str(uuid.uuid4())\n",
    "LANGSMITH_PROJECT=\"pr-crushing-pharmacopoeia-50\"\n",
    "\n",
    "beautify_output = {\n",
    "    GENERATE_CLARIFICATION_QUERY: \"🤔 Understanding your business details...\",\n",
    "    GET_COMPETITORS: \"🔍 Searching for your competitors...\",\n",
    "    GET_COMPETITORS_PROMPT: \"📝 Preparing research queries for each competitor...\",\n",
    "    GET_REPORT: \"📊 Researching competitors in depth (this may take a moment)...\",\n",
    "    FINAL: \"✅ Generating your competitive analysis report...\",\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50eb5c52",
   "metadata": {},
   "outputs": [],
   "source": [
    "class State(TypedDict):\n",
    "    messages: Annotated[List[Any], add_messages]\n",
    "    stage: Literal[\n",
    "        INITIAL,\n",
    "        GENERATE_CLARIFICATION_QUERY,\n",
    "        GET_COMPETITORS,                \n",
    "        GET_COMPETITORS_PROMPT,\n",
    "        GET_REPORT,\n",
    "        CONFIRM_COMPETITORS,\n",
    "        FINAL, \n",
    "    ]\n",
    "    business_info: Optional[str] = Field(description=\"The business info\")\n",
    "    competitors: Optional[List[str]] = Field(description=\"The competitors of the business\")\n",
    "    competitors_query: Optional[Dict[str, str]] = Field(description=\"The query to get the competitors\")\n",
    "    competitors_report: Optional[Dict[str, str]] = Field(description=\"The report of the competitors\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4e5b60c",
   "metadata": {},
   "outputs": [],
   "source": [
    "class LLMResponse(BaseModel):\n",
    "    response: str = Field(description=\"The response to the user's message\")\n",
    "    stage: Optional[Literal[\n",
    "        INITIAL,\n",
    "        GENERATE_CLARIFICATION_QUERY,\n",
    "        GET_COMPETITORS,                \n",
    "        GET_COMPETITORS_PROMPT,\n",
    "        CONFIRM_COMPETITORS,\n",
    "        FINAL\n",
    "    ]] = Field(description=\"The next stage of the workflow\")\n",
    "    business_info: Optional[str] = Field(description=\"The business info\")\n",
    "    competitors: Optional[List[str]] = Field(description=\"The competitors of the business\")\n",
    "    competitors_query: Optional[Dict[str, str]] = Field(description=\"The query to get the competitors\")\n",
    "    competitors_report: Optional[Dict[str, str]] = Field(description=\"The report of the competitors\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "387c6ab4",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "nest_asyncio.apply()\n",
    "async_browser =  create_async_playwright_browser(headless=False)  # headful mode\n",
    "toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=async_browser)\n",
    "tools = toolkit.get_tools()  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "685ecf7f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Bind LLM to tools\n",
    "tools_llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0).bind_tools(tools)\n",
    "struct_llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0).with_structured_output(LLMResponse)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81529736",
   "metadata": {},
   "outputs": [],
   "source": [
    "def formatter(tool_result, state):\n",
    "    \"\"\"\n",
    "    Converts tool-calling LLM results into structured LLMResponse format.\n",
    "    This is needed because we can't use .bind_tools() AND .with_structured_output() \n",
    "    together effectively - they conflict.\n",
    "    Pattern: tools_llm (use tools) → formatter → structured data\n",
    "    \"\"\"\n",
    "    \n",
    "    if hasattr(tool_result, 'content'):\n",
    "        content = tool_result.content\n",
    "    else:\n",
    "        content = str(tool_result)\n",
    "        \n",
    "    current_stage = state.get(\"stage\", \"initial\")\n",
    "    business_info = state.get(\"business_info\", \"None\")\n",
    "    existing_competitors = state.get(\"competitors\", [])\n",
    "    competitors_query = state.get(\"competitors_query\", {})\n",
    "    competitors_report = state.get(\"competitors_report\", {})\n",
    "    \n",
    "    STRUCT_SYSTEM_PROMPT = f\"\"\"\n",
    "    You are a data extractor. Extract information from tool/search results and format into the required schema.\n",
    "    EXTRACTION RULES:\n",
    "    1. Extract ONLY factual information present in the results\n",
    "    2. For competitors: extract as list of company/person names\n",
    "    3. For reports: extract findings as dictionary {{competitor: report}}\n",
    "    4. Do NOT invent, hallucinate, or add information not in the source\n",
    "    5. If data is missing, leave field empty/null\n",
    "    6. Preserve exact names and facts from source\n",
    "    CURRENT CONTEXT:\n",
    "    - Stage: {current_stage}\n",
    "    - Business: {business_info}\n",
    "    - Known Competitors: {existing_competitors}\n",
    "    - Competitors Queries: {competitors_query}\n",
    "    - Competitors Reports: {competitors_report}\n",
    "    OUTPUT: Return structured data matching the schema. No markdown, no explanations.\n",
    "    \"\"\"\n",
    "\n",
    "    structured_result = struct_llm.invoke([\n",
    "        SystemMessage(content=STRUCT_SYSTEM_PROMPT),\n",
    "        HumanMessage(content=f\"Extract and structure this:\\n\\n{content}\")\n",
    "    ])\n",
    "\n",
    "    return structured_result\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a8667b4a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Nodes\n",
    "\n",
    "def get_competitors(state: State) -> State:\n",
    "    \"\"\"Get competitors from the business info with error handling\"\"\"\n",
    "    try:\n",
    "        business_info = state.get(\"business_info\", \"\")\n",
    "        \n",
    "        if not business_info:\n",
    "            return {\n",
    "                \"messages\": [AIMessage(content=\"⚠️ I need your business information first to find competitors.\")],\n",
    "                \"stage\": INITIAL\n",
    "            }\n",
    "        \n",
    "        system_prompt = f\"\"\"You are a competitive intelligence researcher with web search tools.\n",
    "        Business Info: {business_info}\n",
    "        TASK: Find the TOP 3-5 direct competitors for this business.\n",
    "        If there are existing competitors in the competitors field make more research on competitors in the same industry/niche and reshuffle the list. Note max length 5 competitors\n",
    "        Use browser tools to search for competitors in the same industry/niche.\n",
    "        Requirements:\n",
    "        - Competitors can be companies OR individuals\n",
    "        - Only list REAL names and url\n",
    "        - Focus on direct competitors\n",
    "        Return the competitor names clearly.\"\"\"\n",
    "        \n",
    "        result = struct_llm.invoke([\n",
    "            SystemMessage(content=system_prompt),\n",
    "            HumanMessage(content=f\"Find competitors for: {business_info}\")\n",
    "        ])\n",
    "\n",
    "        # result = formatter(result, state)\n",
    "        \n",
    "        competitors = result.competitors if hasattr(result, 'competitors') and result.competitors else []\n",
    "        \n",
    "        if not competitors:\n",
    "            return {\n",
    "                \"messages\": [AIMessage(content=\"⚠️ I couldn't find competitors automatically. Could you tell me who your main competitors are?\")],\n",
    "                \"stage\": GET_COMPETITORS\n",
    "            }\n",
    "        \n",
    "        return {\n",
    "            \"messages\": [AIMessage(content=result.response if hasattr(result, 'response') else f\"Found competitors: {', '.join(competitors)}\")],\n",
    "            \"competitors\": competitors,\n",
    "            \"stage\": CONFIRM_COMPETITORS\n",
    "        }\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"Error in get_competitors: {e}\", flush=True)\n",
    "        return {\n",
    "            \"messages\": [AIMessage(content=\"⚠️ I encountered an error searching for competitors. Could you tell me who your main competitors are?\")],\n",
    "            \"stage\": GET_COMPETITORS\n",
    "        }\n",
    "\n",
    "def get_competitors_prompt(state: State) -> State:\n",
    "    \"\"\"Generate search queries with error handling\"\"\"\n",
    "    try:\n",
    "        business_info = state.get(\"business_info\", \"\")\n",
    "        competitors = state.get(\"competitors\", [])\n",
    "        \n",
    "        if not competitors:\n",
    "            return {\n",
    "                \"messages\": [AIMessage(content=\"⚠️ No competitors to research yet.\")],\n",
    "                \"stage\": GET_COMPETITORS\n",
    "            }\n",
    "        \n",
    "        system_prompt = f\"\"\"You are a research strategist.\n",
    "        Business Info: {business_info}\n",
    "        Competitors: {', '.join(competitors)}\n",
    "        TASK: Generate detailed search queries for EACH competitor considering the business info. using the browser tools\"\"\"\n",
    "        \n",
    "        result = struct_llm.invoke([\n",
    "            SystemMessage(content=system_prompt),\n",
    "            HumanMessage(content=f\"Generate search queries for: {', '.join(competitors)}\")\n",
    "        ])\n",
    "\n",
    "        # result = formatter(result, state)\n",
    "        \n",
    "        queries = result.competitors_query if hasattr(result, 'competitors_query') and result.competitors_query else {}\n",
    "        \n",
    "        if not queries:\n",
    "            queries = {comp: f\"{comp} products services pricing features\" for comp in competitors}\n",
    "        \n",
    "        return {\n",
    "            \"messages\": [AIMessage(content=result.response if hasattr(result, 'response') else \"Search queries generated\")],\n",
    "            \"competitors_query\": queries,\n",
    "            \"stage\": GET_REPORT\n",
    "        }\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"Error in get_competitors_prompt: {e}\", flush=True)\n",
    "        queries = {comp: f\"{comp} business overview\" for comp in state.get(\"competitors\", [])}\n",
    "        return {\n",
    "            \"messages\": [AIMessage(content=\"⚠️ Generated basic search queries.\")],\n",
    "            \"competitors_query\": queries,\n",
    "            \"stage\": GET_COMPETITORS_PROMPT\n",
    "        }\n",
    "\n",
    "def get_report(state: State) -> State:\n",
    "    \"\"\"Get detailed reports with error handling\"\"\"\n",
    "    try:\n",
    "        competitors_query = state.get(\"competitors_query\", {})\n",
    "        competitors = state.get(\"competitors\", [])\n",
    "        \n",
    "        if not competitors_query or not competitors:\n",
    "            return {\n",
    "                \"messages\": [AIMessage(content=\"⚠️ Missing competitor information to generate reports.\")],\n",
    "                \"stage\": INITIAL\n",
    "            }\n",
    "        \n",
    "        message = f\"\"\"You are a competitive intelligence researcher.\n",
    "        Use browser tools to research each competitor.\n",
    "        Competitors Queries: {competitors_query}\n",
    "        Return comprehensive information about each competitor.\"\"\"\n",
    "        \n",
    "        result = struct_llm.invoke([\n",
    "            SystemMessage(content=message),\n",
    "            HumanMessage(content=\"Research all competitors\")\n",
    "        ])\n",
    "        # result = formatter(result, state)\n",
    "        reports = result.competitors_report if hasattr(result, 'competitors_report') and result.competitors_report else {}\n",
    "        \n",
    "        if not reports:\n",
    "            reports = {comp: f\"Research data for {comp}\" for comp in competitors}\n",
    "        \n",
    "        return {\n",
    "            \"messages\": [AIMessage(content=result.response if hasattr(result, 'response') else \"Research complete\")],\n",
    "            \"stage\": FINAL,\n",
    "            \"competitors_report\": reports,\n",
    "        }\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"Error in get_report: {e}\", flush=True)\n",
    "        return {\n",
    "            \"messages\": [AIMessage(content=\"⚠️ Error generating reports. Let me try to provide a basic analysis with available information.\")],\n",
    "            \"stage\": FINAL,\n",
    "            \"competitors_report\": {comp: \"Limited data available\" for comp in state.get(\"competitors\", [])}\n",
    "        }\n",
    "    \n",
    "def planner(state: State) -> State:\n",
    "    \"\"\"Plan the research with error handling\"\"\"\n",
    "    try:\n",
    "        system_prompt = f\"\"\"\n",
    "        You handle competitive business analysis. Be concise, conversational, and professional.\n",
    "        GUIDE:\n",
    "        - Greet politely if greeted.\n",
    "        - If user asks questions about competitors, adding/removing competitors, or comparisons → ALWAYS process naturally.\n",
    "        - If user says \"remove this competitor\", \"add this competitor\", \"what about X competitor\" → Update the competitors list accordingly.\n",
    "        - If question or message is COMPLETELY unrelated to business/competitors → reply: \"I only handle competitor analysis.\"\n",
    "        - Always focus on understanding their business and comparing competitors.\n",
    "        WORKFLOW:\n",
    "        1. **Greeting:** Welcome user and mention you handle competitor analysis.\n",
    "        2. **Business Info:** \n",
    "           - Ask 2-3 clear questions if missing.\n",
    "           - Critical: Understand their business type, scale, market/region, the country they are operating in and what makes them unique.\n",
    "           - Ask targeted questions based on what you already know, not generic ones.\n",
    "           - Ask follow-ups if vague.\n",
    "           - When clear → Summarize in business_info field, move forward.\n",
    "        3. **Competitors:**\n",
    "           - If business info is clear but competitors are missing → set stage='get_competitors'.\n",
    "           - When stage=confirm_competitors → Ask user to confirm/modify the competitors list.\n",
    "           - You can ask user if they wish you to help them add more competitors or remove competitors at this stage.\n",
    "           - User can add more competitors or remove competitors at this stage.\n",
    "           - ONLY after user explicitly confirms (e.g., \"yes\", \"looks good\", \"proceed\", \"continue\") → set stage='get_competitors_prompt'.\n",
    "           - If user modifies list → update competitors field and ask for confirmation again.\n",
    "           - if a user ask you to add more by yourself set stage=get_competitors\n",
    "           - after user confirms the competitors list set stage=get_competitors_prompt\n",
    "       4. **Final Report (READ-ONLY - You cannot SET this stage):**\n",
    "           - You will ONLY see stage=final after the system completes research\n",
    "           - When you see stage=final and competitors_report exists → IMMEDIATELY generate the complete MARKDOWN report\n",
    "           - DO NOT manually set stage=final yourself\n",
    "           - The workflow automatically moves: get_competitors_prompt → get_report → final\n",
    "           - Your job at final: generate the report with:\n",
    "               * Executive summary (2-3 sentences)\n",
    "               * Your business overview\n",
    "               * Each competitor: strengths, weaknesses, differentiators\n",
    "               * Comparison table (You vs Competitors)\n",
    "               * Recommendations: opportunities, differentiation, action plan (3 items)\n",
    "               * Key takeaways (3 points)\n",
    "           - Use headers (##), tables (|), bullets (-), emojis for sections\n",
    "           - Base everything on ACTUAL research data from competitors_report\n",
    "           - After generating the report set stage=initial\n",
    "        CRITICAL RULES:\n",
    "        - ALWAYS handle competitor modification requests (add/remove/change).\n",
    "        - When user confirms competitors → set stage=get_competitors_prompt\n",
    "        - Stay on topic and professional.\n",
    "        - Don't change stage until confirmed or task is ready.\n",
    "        - Be brief, clear, and business-focused.\n",
    "        - When stage=final, generate report IMMEDIATELY without asking and then set stage=initial.\n",
    "        CURRENT STATE:\n",
    "        - Stage: {state.get(\"stage\", \"NONE\")}\n",
    "        - Business Info: {state.get(\"business_info\", \"NONE\")}\n",
    "        - Competitors: {\", \".join(state.get(\"competitors\", [])) or \"NONE\"}\n",
    "        \"\"\"\n",
    "        \n",
    "        messages = [\n",
    "            SystemMessage(content=system_prompt),\n",
    "            *state.get(\"messages\", []),\n",
    "        ]\n",
    "        \n",
    "        if state.get(\"stage\") == GET_COMPETITORS_PROMPT or state.get(\"stage\") == GET_REPORT:\n",
    "            return state\n",
    "        \n",
    "        result = struct_llm.invoke(messages)\n",
    "        \n",
    "        return {\n",
    "            \"messages\": [AIMessage(content=result.response if hasattr(result, 'response') else \"Let me help you with competitor analysis.\")],\n",
    "            \"stage\": result.stage if hasattr(result, 'stage') and result.stage else INITIAL,\n",
    "            \"business_info\": result.business_info if hasattr(result, 'business_info') and result.business_info else state.get(\"business_info\", \"\"),\n",
    "            \"competitors\": result.competitors if hasattr(result, 'competitors') and result.competitors else state.get(\"competitors\", []),\n",
    "            \"competitors_query\": result.competitors_query if hasattr(result, 'competitors_query') and result.competitors_query else state.get(\"competitors_query\", {}),\n",
    "            \"competitors_report\": result.competitors_report if hasattr(result, 'competitors_report') and result.competitors_report else state.get(\"competitors_report\", {})\n",
    "        }\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"Error in planner: {e}\", flush=True)\n",
    "        return {\n",
    "            \"messages\": [AIMessage(content=\"⚠️ I encountered an issue. Let's start fresh - tell me about your business.\")],\n",
    "            \"stage\": INITIAL,\n",
    "            \"business_info\": state.get(\"business_info\", \"\"),\n",
    "            \"competitors\": state.get(\"competitors\", []),\n",
    "            \"competitors_query\": state.get(\"competitors_query\", {}),\n",
    "            \"competitors_report\": state.get(\"competitors_report\", {})\n",
    "        }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c6f53629",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Node conditions\n",
    "\n",
    "def node_conditions(state:State):\n",
    "    if state.get(\"stage\") == GET_COMPETITORS:\n",
    "        return GET_COMPETITORS\n",
    "    elif state.get(\"stage\") == GET_COMPETITORS_PROMPT:\n",
    "        return GET_COMPETITORS_PROMPT\n",
    "    elif state.get(\"stage\") == GET_REPORT:\n",
    "        return GET_REPORT\n",
    "    else:\n",
    "        return \"END\"\n",
    "\n",
    "def tools_routing(state:State):\n",
    "    if state.get(\"stage\") == GET_COMPETITORS:\n",
    "        return GET_COMPETITORS\n",
    "    elif state.get(\"stage\") == GET_COMPETITORS_PROMPT:\n",
    "        return GET_COMPETITORS_PROMPT\n",
    "    elif state.get(\"stage\") == GET_REPORT:\n",
    "        return GET_REPORT\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f32202de",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Graph\n",
    "\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "graph_builder.add_node(\"get_competitors\", get_competitors)\n",
    "graph_builder.add_node(\"get_competitors_prompt\", get_competitors_prompt)\n",
    "\n",
    "graph_builder.add_node(\"get_report\", get_report)\n",
    "graph_builder.add_node(\"tools\", ToolNode(tools=tools))\n",
    "graph_builder.add_node(\"planner\", planner)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a8bdcc64",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Edges\n",
    "\n",
    "graph_builder.add_edge(START, \"planner\")\n",
    "graph_builder.add_conditional_edges(\"planner\", node_conditions, {GET_COMPETITORS: \"get_competitors\", GET_COMPETITORS_PROMPT: \"get_competitors_prompt\", GET_REPORT: \"get_report\", \"END\": END})\n",
    "graph_builder.add_conditional_edges(\"get_competitors\", tools_condition, {\"tools\": \"tools\", END: \"planner\"})\n",
    "graph_builder.add_conditional_edges(\"get_competitors_prompt\", tools_condition, {\"tools\": \"tools\", END: \"planner\"})\n",
    "graph_builder.add_conditional_edges(\"get_report\", tools_condition, {\"tools\": \"tools\", END: \"planner\"})\n",
    "graph_builder.add_conditional_edges(\"tools\", tools_routing, {GET_COMPETITORS: \"get_competitors\", GET_COMPETITORS_PROMPT: \"get_competitors_prompt\", GET_REPORT: \"get_report\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff593b2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "memory = MemorySaver()\n",
    "graph = graph_builder.compile(checkpointer=memory)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f1b693c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "423ace28",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "def reset_conversation():\n",
    "    \"\"\"Reset to new conversation\"\"\"\n",
    "    global THREAD_ID\n",
    "    THREAD_ID = str(uuid.uuid4())\n",
    "    return None, \"\" \n",
    "\n",
    "def compat_message(message, owner):\n",
    "    return {\"role\": owner, \"content\": message}\n",
    "\n",
    "async def chat(message, history):\n",
    "    try:\n",
    "        yield [*history, compat_message(message=message, owner=\"user\"), compat_message(message=\"✨ Thinking ...\", owner=\"assistant\")]\n",
    "        if not message or message.strip() == \"\":\n",
    "            yield [*history, compat_message(message=message, owner=\"user\"), compat_message(message=\"Please enter a message.\", owner=\"assistant\")]\n",
    "        \n",
    "        config = {\"configurable\": {\"thread_id\": THREAD_ID}}\n",
    "        state = {\"messages\": [HumanMessage(content=message)]}\n",
    "        \n",
    "        result = None\n",
    "        async for event in graph.astream(state, config, stream_mode=\"values\"):\n",
    "            result = event\n",
    "            stage = event.get(\"stage\", \"\")\n",
    "            if stage:\n",
    "                beautified_text = beautify_output.get(stage, \"\")\n",
    "                if beautified_text:\n",
    "                    yield [*history, compat_message(message=message, owner=\"user\"), compat_message(message=beautified_text, owner=\"assistant\")]\n",
    "        \n",
    "        if not result or \"messages\" not in result or not result[\"messages\"]:\n",
    "            yield [*history, compat_message(message=message, owner=\"user\"), compat_message(message=\"⚠️ I didn't receive a proper response. Please try again.\", owner=\"assistant\")]\n",
    "        \n",
    "        yield [*history, compat_message(message=message, owner=\"user\"), compat_message(message=result[\"messages\"][-1].content, owner=\"assistant\")]\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"Chat error: {e}\", flush=True)\n",
    "        yield [*history, compat_message(message=message, owner=\"user\"), compat_message(message=\"⚠️ Something went wrong. Please try again or start a new conversation.\", owner=\"assistant\")]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12aa082e",
   "metadata": {},
   "outputs": [],
   "source": [
    "with gr.Blocks(theme=gr.themes.Soft(), title=\"🔍 Competitor Analysis Agent\") as app:\n",
    "    gr.Markdown(\"\"\"\n",
    "    # 🔍 Competitive Analysis Agent\n",
    "    \n",
    "    Get detailed insights about your competitors and actionable recommendations for your business.\n",
    "    \n",
    "    **How it works:**\n",
    "    1. Tell me about your business\n",
    "    2. I'll find and research your competitors\n",
    "    3. Get a comprehensive analysis report\n",
    "    \"\"\")\n",
    "    \n",
    "    with gr.Row():\n",
    "        with gr.Column(scale=2):\n",
    "            chatbot = gr.Chatbot(\n",
    "                label=\"Chat\",\n",
    "                height=500,\n",
    "                type=\"messages\",  # Better message display\n",
    "                show_label=False\n",
    "            )\n",
    "            \n",
    "            with gr.Row():\n",
    "                msg = gr.Textbox(\n",
    "                    label=\"Your message\",\n",
    "                    placeholder=\"Tell me about your business...\",\n",
    "                    show_label=\"Your message\",\n",
    "                    submit_btn=\" Send \",\n",
    "                    scale=4 \n",
    "                )\n",
    "            \n",
    "            with gr.Row():\n",
    "                clear_btn = gr.Button(\"🔄 New Conversation\", variant=\"secondary\")\n",
    "                \n",
    "        with gr.Column(scale=1):\n",
    "            gr.Markdown(\"### 💡 Tips\")\n",
    "            gr.Markdown(\"\"\"\n",
    "            - Be specific about your business\n",
    "            - Mention your target market\n",
    "            - Include your location/region\n",
    "            - Tell me your business scale\n",
    "            \n",
    "            ### 📝 Example Questions\n",
    "            - \"I run a freelance web design business\"\n",
    "            - \"Help me analyze my competitors\"\n",
    "            - \"I'm a solo consultant in Nigeria\"\n",
    "            \"\"\")\n",
    "    \n",
    "    # Event handlers\n",
    "    msg.submit(chat, [msg, chatbot], chatbot, queue=True)\n",
    "    msg.submit(lambda:\"\", outputs=msg)\n",
    "    clear_btn.click(reset_conversation, None, [chatbot, msg])\n",
    "\n",
    "app.launch()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
